package utils

import (
	"fmt"
	"os"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type SafeLogger struct {
	push_cnt                int32
	push_fail_cnt           int32
	check_flag              int32
	check_overload_output_t time.Time
	cur_write               interface{}
	cur_write_start_t       time.Time

	defaultSkip int
	maxcachenum int
	cachelist   *SyncQueue
	listener    *SafeList
	closeFlag   int
	closeW      sync.WaitGroup
}

func GetLogOp(opstr string) LogOp {
	matchStr := strings.ToUpper(opstr)
	if matchStr == "=" || matchStr == "==" || matchStr == "EQ" || matchStr == "EQUAL" {
		return LogOp_EQ
	} else if matchStr == "ALL" {
		return LogOp_ALL
	} else if matchStr == ">=" {
		return LogOp_GT
	} else if matchStr == "<=" {
		return LogOp_LT
	} else {
		return LogOp_GT
	}

}

func GetLogLevel(lvlstr string) LogLevel {
	matchStr := strings.ToUpper(lvlstr)
	if matchStr == "LV0" {
		return LogLV_0
	} else if matchStr == "LV1" {
		return LogLV_1
	} else if matchStr == "DEBUG" || matchStr == "DEBG" || matchStr == "LV2" {
		return LogLV_DEBUG
	} else if matchStr == "TRACE" || matchStr == "TRAC" || matchStr == "LV3" {
		return LogLV_TRACE
	} else if matchStr == "INFO" || matchStr == "LV4" {
		return LogLV_INFO
	} else if matchStr == "WARN" || matchStr == "LV5" {
		return LogLV_WARN
	} else if matchStr == "ERROR" || matchStr == "EROR" || matchStr == "LV6" {
		return LogLV_ERROR
	} else if matchStr == "FAIL" || matchStr == "LV7" {
		return LogLV_7
	} else {
		return LogLV_INFO
	}
}

func NewSafeLogger(cacheNum int) *SafeLogger {
	rval := &SafeLogger{
		defaultSkip: 1,
		maxcachenum: cacheNum,
		cachelist:   NewSyncQueue(),
		listener:    NewSafeList(),
	}
	rval.innerStart()
	return rval
}

func (this *SafeLogger) Close() {
	this.closeFlag = 1
	this.cachelist.Push(nil)
	this.closeW.Wait()
}

func (this *SafeLogger) SetDefaultSkip(CallstackSkip int) {
	this.defaultSkip = CallstackSkip
}

func (this *SafeLogger) innerStart() {
	this.closeFlag = 0
	GoFunc2(func() {
		this.closeW.Add(1)
		defer this.closeW.Done()
		for this.closeFlag == 0 {
			if ok, v0 := this.cachelist.Pop(); ok {
				if v0 == nil {
					break
				}
				rec := v0.(*LogRecord)
				this.listener.ForEach(func(val interface{}) {
					this.cur_write = val
					this.cur_write_start_t = time.Now()
					val.(ILogWriter).LogWrite(rec)
				})
			} else {
				time.Sleep(time.Millisecond * 100)
			}
		}

		this.listener.ForEach(func(val interface{}) {
			val.(ILogWriter).Close()
		})

	})
}

func (this *SafeLogger) Status() string {
	return fmt.Sprintf("writer num:%d, cache:%d, push:%d, push fail:%d", this.listener.Count(), this.cachelist.Count(), this.push_cnt, this.push_fail_cnt)
}

func (this *SafeLogger) AddWriter(writer ILogWriter) {
	this.listener.Add(writer)
}

func (this *SafeLogger) RemoveWriter(writer ILogWriter) {
	this.listener.Remove(writer)
}

func (this *SafeLogger) Debug(args ...interface{}) {
	this.LogMsgf("", LogLV_DEBUG, this.defaultSkip+1, "", args...)
}

func (this *SafeLogger) Info(args ...interface{}) {
	this.LogMsgf("", LogLV_INFO, this.defaultSkip+1, "", args...)
}

func (this *SafeLogger) Err(args ...interface{}) {
	this.LogMsgf("", LogLV_ERROR, this.defaultSkip+1, "", args...)
}

func (this *SafeLogger) Warn(args ...interface{}) {
	this.LogMsgf("", LogLV_WARN, this.defaultSkip+1, "", args...)
}

func (this *SafeLogger) Errf(format string, args ...interface{}) {
	this.LogMsgf("", LogLV_ERROR, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) Debugf(format string, args ...interface{}) {
	this.LogMsgf("", LogLV_DEBUG, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) Infof(format string, args ...interface{}) {
	this.LogMsgf("", LogLV_INFO, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) Warnf(format string, args ...interface{}) {
	this.LogMsgf("", LogLV_WARN, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) Logf(lvl LogLevel, format string, args ...interface{}) {
	this.LogMsgf("", lvl, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) ErrTypef(typestr, format string, args ...interface{}) {
	this.LogMsgf(typestr, LogLV_ERROR, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) DebugTypef(typestr, format string, args ...interface{}) {
	this.LogMsgf(typestr, LogLV_DEBUG, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) InfoTypef(typestr, format string, args ...interface{}) {
	this.LogMsgf(typestr, LogLV_INFO, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) WarnTypef(typestr, format string, args ...interface{}) {
	this.LogMsgf(typestr, LogLV_WARN, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) LogTypef(typestr string, lvl LogLevel, format string, args ...interface{}) {
	this.LogMsgf(typestr, lvl, this.defaultSkip+1, format, args...)
}

func (this *SafeLogger) LogMsgRaw(typestr string, raw []byte, format string, args ...interface{}) {
	msg := format
	if len(args) > 0 {
		msg = fmt.Sprintf(format, args...)
	}

	rec := &LogRecord{
		Typestr: typestr,
		Level:   LogLV_INFO,
		Created: time.Now(),
		Msg:     msg,
		Buf:     raw,
	}
	this.innerPushCache(rec)
}

func (this *SafeLogger) innerPushCache(rec *LogRecord) {
	l := int(this.cachelist.Count())
	if this.maxcachenum > 0 && l >= this.maxcachenum {
		if atomic.CompareAndSwapInt32(&this.check_flag, 0, 1) { // 输出错误日志
			if this.check_overload_output_t.IsZero() || time.Since(this.check_overload_output_t).Seconds() > 120 {
				fmt.Fprintf(os.Stderr, "[%s]日志堆积[%d]已经满了, 最大缓存数量[%d], 压入日志将被忽略, 当前日志执行:%v, 耗时:%d ms\n", NowString(), l, this.maxcachenum,
					this.cur_write, time.Since(this.cur_write_start_t).Milliseconds())
				this.check_overload_output_t = time.Now()
			}
			this.check_flag = 0
		}
		atomic.AddInt32(&this.push_fail_cnt, 1)
		return
	}
	atomic.AddInt32(&this.push_cnt, 1)
	this.cachelist.Push(rec)
}

func (this *SafeLogger) Msgf(typestr string, lvl LogLevel, callskip int, format string, args ...interface{}) {
	this.LogMsgf(typestr, lvl, this.defaultSkip+1+callskip, format, args...)
}

func (this *SafeLogger) LogMsgf(typestr string, lvl LogLevel, callstackskip int, format string, args ...interface{}) {
	pc, _, lineno, ok := runtime.Caller(callstackskip)
	src := ""
	if ok {
		src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno)
	}

	msg := format
	if len(msg) == 0 {
		msg = fmt.Sprint(args...)
	} else {
		if len(args) > 0 {
			msg = fmt.Sprintf(format, args...)
		}
	}

	// Make the log record
	rec := &LogRecord{
		Typestr: typestr,
		Level:   lvl,
		Created: time.Now(),
		Source:  src,
		Msg:     msg,
	}
	this.innerPushCache(rec)
}
